#include <cstring>
#include <unistd.h>
#include <stdio.h> 
#include <iostream> 
#include <stdlib.h>

#include "terminal.h"
#include "data_models.h"

#define KEYCODE_CTRL_Q 17
#define KEYCODE_CTRL_S 19
#define TERMIOS_CLEAR_ALL "\x1b[2J",4
#define TERMIOS_HIDE_CURSOR "\x1b[?25l",6
#define TERMIOS_SHOW_CURSOR "\x1b[?25h",6
#define TERMIOS_CURSOR_GOTO_1_1 "\x1b[H",3
#define TERMIOS_CLEAR_CURRENT_LINE "\x1b[K",4
//#define TERMIOS_CURSOR_GOTO_X_Y(X,Y) "\x1b[10;10H",8



#define COLOR_STYLE_COMMENT COLOR_COMMENT,5
#define COLOR_STYLE_KEYWORD1 COLOR_KEYWORD1,5
#define COLOR_STYLE_KEYWORD2 COLOR_KEYWORD2,5
#define COLOR_STYLE_STRING COLOR_STRING,5
#define COLOR_STYLE_NUMBER COLOR_NUMBER,5
#define COLOR_STYLE_MATCH COLOR_MATCH,5
#define COLOR_STYLE_DEFAULT COLOR_DEFAULT,4
#define COLOR_STYLE_INVERT COLOR_INVERT,4

//#define COLOR_STYLE_COMMENT "\x1b[36m",5
//#define COLOR_STYLE_KEYWORD1 "\x1b[33m",5
//#define COLOR_STYLE_KEYWORD2 "\x1b[32m",5
//#define COLOR_STYLE_STRING "\x1b[35m",5
//#define COLOR_STYLE_NUMBER "\x1b[31m",5
//#define COLOR_STYLE_MATCH "\x1b[34m",5
//#define COLOR_STYLE_DEFAULT "\x1b[0m",4
//#define COLOR_STYLE_INVERT "\x1b[7m",4






#define ESC 27 //Key code for ESC

using namespace std;



Terminal::Terminal(){
    if (getWindowSize(STDIN_FILENO,STDOUT_FILENO,
                      &_size.Height,&_size.Width) == -1) {
        perror("Unable to query the screen for size (columns / rows)");
        _exit(1);
    }
    _size.Height -= 2;
}

Terminal::~Terminal(){
}


/* reference:https://github.com/antirez/kilo/blob/master/kilo.c
 * Try to get the number of columns in the current terminal. If the ioctl()
 * call fails the function will try to query the terminal itself.
 * Returns 0 on success, -1 on error. */
int Terminal::getWindowSize(int ifd, int ofd, int *rows, int *cols) {
    struct winsize ws;
    if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) {
        /* ioctl() failed. Try to query the terminal itself. */
        int orig_row, orig_col, retval;

        /* Get the initial position so we can restore it later. */
        retval = getCursorPosition(ifd,ofd,&orig_row,&orig_col);
        if (retval == -1) goto failed;

        /* Go to right/bottom margin and get position. */
        if (write(ofd,"\x1b[999C\x1b[999B",12) != 12) goto failed;
        retval = getCursorPosition(ifd,ofd,rows,cols);
        if (retval == -1) goto failed;

        /* Restore position. */
        char seq[32];
        snprintf(seq,32,"\x1b[%d;%dH",orig_row,orig_col);
        if (write(ofd,seq,strlen(seq)) == -1) {
            /* Can't recover... */
        }
        return 0;
    } else {
        *cols = ws.ws_col;
        *rows = ws.ws_row;
        return 0;
    }

failed:
    return -1;
}






/* reference:https://github.com/antirez/kilo/blob/master/kilo.c
 * Use the ESC [6n escape sequence to query the horizontal cursor position
 * and return it. On error -1 is returned, on success the position of the
 * cursor is stored at *rows and *cols and 0 is returned. */
int Terminal::getCursorPosition(int ifd, int ofd, int *rows, int *cols) {
    char buf[32];
    unsigned int i = 0;

    /* Report cursor location */
    if (write(ofd, "\x1b[6n", 4) != 4) return -1;

    /* Read the response: ESC [ rows ; cols R */
    while (i < sizeof(buf)-1) {
        if (read(ifd,buf+i,1) != 1) break;
        if (buf[i] == 'R') break;
        i++;
    }
    buf[i] = '\0';

    /* Parse it. */
    if (buf[0] != ESC || buf[1] != '[') return -1;
    if (sscanf(buf+2,"%d;%d",rows,cols) != 2) return -1;
    return 0;
}



TerminalSize Terminal::Size(){
   return _size;
}


void Terminal::ClearScreen(){
    write(STDOUT_FILENO,TERMIOS_CLEAR_ALL);
}

void Terminal::CursorPosition(Position position){
    char buffer[100];
    int x = position.x+1;
    int y = position.y+1;

    setCursorPosStr(buffer,x,y);
    write(STDOUT_FILENO,buffer,getCursorPosLen(x,y));
}

int Terminal::getCursorPosLen(unsigned x,unsigned t){
    int x_len = numlen(x);
    int t_len = numlen(t);
    return 4+x_len+t_len;
}

void Terminal::setCursorPosStr(char* buffer,unsigned x,unsigned t){
    snprintf(buffer,100,"\x1b[%d;%dH",x,t);
}



int Terminal::numlen(int num){
    int len = 0;
    for(;num>0;++len){
        num /= 10;
    }
    return len;
}

void Terminal::CursorHide(){
    write(STDOUT_FILENO,TERMIOS_HIDE_CURSOR);
}

void Terminal::CursorShow(){
    write(STDOUT_FILENO,TERMIOS_SHOW_CURSOR);
}

void Terminal::ClearCurrentLine(){
    write(STDOUT_FILENO,TERMIOS_CLEAR_CURRENT_LINE);
}



void Terminal::Flush(){
    std::cout.flush();
}

void Terminal::SetStyle(int style){
    switch(style){
        case ColorStyle::Comment:{
            write(STDOUT_FILENO,COLOR_STYLE_COMMENT);
        }break;
        case ColorStyle::Keyword2:{
            write(STDOUT_FILENO,COLOR_STYLE_KEYWORD2);
        }break;
        case ColorStyle::Keyword1:{
            write(STDOUT_FILENO,COLOR_STYLE_KEYWORD1);
        }break;
        case ColorStyle::String:{
            write(STDOUT_FILENO,COLOR_STYLE_STRING);
        }break;
        case ColorStyle::Number:{
            write(STDOUT_FILENO,COLOR_STYLE_NUMBER);
        }break;
        case ColorStyle::Match:{
            write(STDOUT_FILENO,COLOR_STYLE_MATCH);
        }break;
        case ColorStyle::Default:{
            write(STDOUT_FILENO,COLOR_STYLE_DEFAULT);
        }break;
        case ColorStyle::Invert:{
            write(STDOUT_FILENO,COLOR_STYLE_INVERT);
        }break;

    }
}


void Terminal::ResetStyle(){
    SetStyle(ColorStyle::Default);
}






/*
void Terminal::aa(){

    write(STDOUT_FILENO,"\x1b[7m",4);
    write(STDOUT_FILENO,"aaaa",4);

    write(STDOUT_FILENO,"\x1b[38m",5);
    write(STDOUT_FILENO,"aaaa",4);
    return;
   }
   */
